﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Platform.Linq;
using Platform.ObjectModel;
using Platform.ObjectModel.Design;
using Platform.Storage.Impl;

namespace Platform.Storage.Entity
{

	public sealed class EntityLibraryStorage : ILibraryStorage
	{
		private Lazy<DataObjectContext> context = new Lazy<DataObjectContext>( false );
		private Library library;
		private IQueryProvider queryProvider;
		private IFormatProvider cultureDefault;

		public EntityLibraryStorage()
		{
			//this.queryProvider = new DynamicQueryProvider( new EntityQueryTranslator(), new EntityDataObjectMaterializer() );
			this.cultureDefault = System.Globalization.CultureInfo.InvariantCulture;
		}

		internal DataObjectContext Context
		{
			get { return this.context.Value; }
		}

		#region ILibraryStorage Members

		public void Init( Library lib )
		{
			this.library = lib;
			this.queryProvider = new DynamicQueryProvider(  this.Context, lib );
		}

		public SearchStorageCapabilities Capabilities
		{
			get
			{
				return SearchStorageCapabilities.ExactMatch
					| SearchStorageCapabilities.ComparerOperands
					| SearchStorageCapabilities.LogicalOperands
					| SearchStorageCapabilities.WildcardMatch;
			}
		}

		public IQueryable<TData> Get<TData>() where TData : DataObjectFlake
		{
			EntityDynamicQuery<TData> query = new EntityDynamicQuery<TData>( this.Context.Fields, this.Context.Relationships, this.queryProvider, this.library );

			return query;
		}

		public void Remove( ObjectModel.DocumentToken tokenID )
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Tamanho máximo de um valor 
		/// </summary>
		//private const int MaxValueLenPerField = 8000;
		//private const int 

		public void InsertOrUpdate( ObjectModel.DocumentContext context )
		{
			// Como fazer cast entre string64 para binary
			// http://blogs.msdn.com/b/sqltips/archive/2008/06/30/converting-from-base64-to-varbinary-and-vice-versa.aspx
			// http://msdn.microsoft.com/en-us/library/ms189075(SQL.100).aspx

			// TODO: Permitir que seja salvo as propriedades que não foram declaradas no schema do documento
			//var document = context.Document;
			//var schema = context.Schema;

			// Cria o XML com os dados
			StringBuilder rawData = new StringBuilder();
			using ( XmlTextWriter data = new XmlTextWriter( new StringWriter( rawData ) ) )
			{
				data.WriteStartElement( "doc" );
				{
					int count = context.StorageDocument.Count;

					for ( int index = 0; index < count; index++ )
					{
						var document = context.StorageDocument[ index ].Document;
						var schema = context.StorageDocument[ index ].Schema;


						// Campos e Valores
						data.WriteStartElement( "fields" );
						{
							foreach ( var field in schema.Fields )
							{
								object value = document[ field.QualifiedName ];
								data.WriteStartElement( "f" );
								data.WriteAttributeString( "t", document.Token.ReversedValue );
								data.WriteAttributeString( "name", schema.QualifiedName.GroupWith( field.QualifiedName ) );
								data.WriteAttributeString( "original", field.QualifiedName.FullName );
								data.WriteAttributeString( "txt", Convert.ToString( value, this.cultureDefault ) );
								//data.WriteAttributeString( "bin", field.CastObjectToBase64String( value ) );
								data.WriteAttributeString( "bin", field.GetSerializer().ToBase64String( value ) );
								data.WriteEndElement();
							}
						}
						data.WriteEndElement();
					}

					for ( int index = 0; index < count; index++ )
					{
						var document = context.StorageDocument[ index ].Document;
						var schema = context.StorageDocument[ index ].Schema;

						// Relacionamentos
						data.WriteStartElement( "refs" );
						{
							// Para cada relação declarada no schema
							foreach ( var relation in schema.Relations )
							{
								// Para cada documento filho da relação
								int order = 0;
								foreach ( var child in LibraryUtility.GetChildren( document, relation.QualifiedName ) )
								{
									data.WriteStartElement( "r" );
									data.WriteAttributeString( "p-name", schema.QualifiedName.GroupWith( relation.QualifiedName ) );
									data.WriteAttributeString( "p-orig", relation.QualifiedName.FullName );
									data.WriteAttributeString( "p-token", document.Token.ReversedValue );
									data.WriteAttributeString( "order", order.ToString() );
									//data.WriteAttributeString( "c-name", relation.ChildSchema.FullName );
									//data.WriteAttributeString( "c-name", child.QualifiedName.FullName );
									data.WriteAttributeString( "c-name", child.SchemaName.FullName );
									data.WriteAttributeString( "c-token", child.Token.ReversedValue );
									data.WriteEndElement();

									order++;
								}
							}
						}
						data.WriteEndElement();
					}
				}
				data.WriteEndElement();
				data.Flush();
			}

			// Salva no banco de dados
			using ( var cmd = this.Context.Database.Connection.CreateCommand() )
			{
				cmd.CommandText = "[platform].pInsertAndUpdateFieldValues";
				cmd.CommandType = System.Data.CommandType.StoredProcedure;

				//	cmd.Parameters.Add( new System.Data.SqlClient.SqlParameter( "@ReversedTokenID", document.Token.ReversedValue ) );
				cmd.Parameters.Add( new System.Data.SqlClient.SqlParameter( "@FieldAndValues", rawData.ToString() ) );

				try
				{
					this.Context.Database.Connection.Open();
					cmd.ExecuteNonQuery();
				}
				finally
				{
					if ( this.Context.Database.Connection.State != System.Data.ConnectionState.Closed )
						this.Context.Database.Connection.Close();
				}
			}
		}


		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if ( this.context.IsValueCreated )
				this.context.Value.Dispose();
		}

		#endregion
	}
}
